#include "media_video_manager.h"

#include <algorithm>
#include <inttypes.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include <vector>
#include <cassert>
#include <memory>
#include <utility>
#include "utils_log.h"
#include "common_config_manager.h"
#include "hal_venc.h"
#include "utils_time.h"

namespace El {
namespace Media {

VideoStreamManager &VideoStreamManager::GetInstance(int32_t ch, int32_t type)
{
    static std::map<std::pair<int32_t, int32_t>, VideoStreamManager *> instances;
    static std::mutex mutex;

    std::lock_guard<std::mutex> lock(mutex);
    auto key = std::make_pair(ch, type);
    auto it = instances.find(key);
    if (it == instances.end()) {
        VideoStreamManager *instance = new VideoStreamManager(ch, type);
        instances.emplace(key, instance);
        return *instance;
    }
    return *it->second;
}

VideoStreamManager::VideoStreamManager(int32_t ch, int32_t type) : ch_(ch), type_(type) {}

VideoStreamManager::~VideoStreamManager() {}

int32_t VideoStreamManager::Start(const Proc &proc)
{
    if (!start_) {
        // 启动视频
        if (!StartVideo()) {
            EL_ERROR("start video encoder ch:{}, type:{} failed", ch_, type_);
            return -1;
        }
        start_ = true;
        videoThread_ = std::make_unique<std::thread>(&VideoStreamManager::VideoThread, this);
    }

    EL_INFO("start video encoder ch:{}, type:{} succeed", ch_, type_);
    return callbackManager_.Register(proc);
}

bool VideoStreamManager::StartVideo()
{
    auto videoConfig = Common::ConfigManager::GetInstance().GetConfig("/video_encode/" + std::to_string(ch_) + "/" +
                                                                      std::to_string(type_));
    if (videoConfig.is_null()) {
        EL_ERROR("Failed to get video config");
        return false;
    }
    EL_INFO("start video: ch {} type {} config {}", ch_, type_, videoConfig.dump(4).c_str());

    HAL_VENC_CONFIG_S stVencConfig;
    memset(&stVencConfig, 0, sizeof(HAL_VENC_CONFIG_S));
    stVencConfig.u32Width = videoConfig.value("width", 0);
    stVencConfig.u32Height = videoConfig.value("height", 0);
    stVencConfig.eProtocol = STREAM_ENCODE_TYPE_H264;
    stVencConfig.u8Fps = videoConfig.value("fps", 25);
    stVencConfig.stH264.bSvc = false;
    stVencConfig.stH264.eRcmode = VENC_RCMODE_CBR;
    stVencConfig.stH264.u32Kbps = videoConfig.value("bitRate", 2048);
    stVencConfig.stH264.u8Profile = 0;
    stVencConfig.stH264.u8Gop = videoConfig.value("gop", 50);

    int32_t ret = Hal_Venc_Start(ch_, type_, &stVencConfig);
    if (ret != HAL_SUCCESS) {
        EL_ERROR("Failed to start video encoder");
        return false;
    }

    return true;
}

void VideoStreamManager::StopVideo()
{
    int32_t ret = Hal_Venc_Stop(ch_, type_);
    if (ret != HAL_SUCCESS) {
        EL_ERROR("Failed to stop video encoder");
        return;
    }
    EL_INFO("stop video encoder ch:{}, type:{} succeed", ch_, type_);
}

void VideoStreamManager::Stop(int32_t handle)
{
    callbackManager_.Unregister(handle);
    // if (callbackManager_.IsEmpty()) {
    //     StopVideo();

    //     start_ = false;
    //     if (videoThread_) {
    //         videoThread_->join();
    //         videoThread_.reset();
    //     }
    // }

    EL_INFO("VideoStreamManager has stopped ch:{}, type:{}", ch_, type_);
}

void VideoStreamManager::VideoThread()
{
    prctl(PR_SET_NAME, "VideoMgrThd", 0, 0, 0);
    while (start_) {
        HAL_VENC_STREAM_S videoFrame;
        memset(&videoFrame, 0, sizeof(HAL_VENC_STREAM_S));
        int32_t ret = Hal_Venc_GetStream(ch_, type_, &videoFrame);
        if (ret != HAL_SUCCESS) {
            EL_ERROR("Failed to get video stream ch:{}, type:{}, ret=0x{:x}", ch_, type_, ret);
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            continue;
        }

        uint32_t videoFrameSize = 0;
        for (uint32_t i = 0; i < videoFrame.u8PackNum; ++i) {
            videoFrameSize += videoFrame.stPack[i].u32DataLen;
        }

        auto media = std::make_shared<MediaFrame>();
        if (!media->SetSize(videoFrameSize)) {
            EL_ERROR("Failed to set media frame size");
            Hal_Venc_ReleaseStream(ch_, type_, videoFrame.pRelHandler);
            continue;
        }
        for (uint32_t i = 0; i < videoFrame.u8PackNum; ++i) {
            media->PutBuffer(videoFrame.stPack[i].pData, videoFrame.stPack[i].u32DataLen);
        }
        if (videoFrame.s8Flag == 1) {
            media->SetFrameType(MEDIA_FRAME_I);
        } else {
            media->SetFrameType(MEDIA_FRAME_P);
        }
        media->SetCodecType(MEDIA_VIDEO_CODEC_H264);
        media->SetPts(videoFrame.u64TimeStamp);
        media->SetUtc(Utils::TimeManager::GetInstance().GetCurrentUtc());
        callbackManager_.InvokeAll(media);

        Hal_Venc_ReleaseStream(ch_, type_, videoFrame.pRelHandler);
    }
    EL_INFO("StreamSource thread is stopping ch:{}, type:{}", ch_, type_);
}

bool VideoStreamManager::ForceIframe()
{
    int32_t ret = Hal_Venc_ForceIframe(ch_, type_);
    if (ret != HAL_SUCCESS) {
        EL_ERROR("Failed to force I-frame");
        return false;
    }
    return true;
}

} // namespace Media
} // namespace El
